<?php
namespace Easy\Db;

use Easy\Db\Driver\XPdo;

class XDbDriver
{
    protected $_values   = [];
    protected $_fields   = [];
    protected $_orFields = [];
    protected $_whereStr = '';

    const SQL_IN_EXP      = 'in';
    const SQL_NOTIN_EXP   = 'notin';
    const SQL_LIKE_EXP    = 'like';
    const SQL_NOTLIKE_EXP = 'notlike';
    const SQL_EQ_EXP      = 'eq';

    public static $inExpArr   = [self::SQL_IN_EXP, self::SQL_NOTIN_EXP];
    public static $likeExpArr = [self::SQL_LIKE_EXP, self::SQL_NOTLIKE_EXP];
    public static $mysqlFun   = ['count', 'sum', 'min', 'max', 'avg'];

    public static $_comparison
        = [
            self::SQL_EQ_EXP      => '=',
            'neq'                 => '!=',
            'gt'                  => '>',
            'egt'                 => '>=',
            'lt'                  => '<',
            'elt'                 => '<=',
            self::SQL_NOTLIKE_EXP => 'NOT LIKE',
            self::SQL_LIKE_EXP    => 'LIKE',
            self::SQL_IN_EXP      => 'IN',
            self::SQL_NOTIN_EXP   => 'NOT IN',
        ];

    public static $_valComparison
        = [
            'gt&lt'               => '/^\(([^,]){1,},([^,]){1,}\)$/',//开区间  in为字段，(1,2) [2016-01-02,2017-09-08]
            'gt&elt'              => '/^\(([^,]){1,},([^,]){1,}\]$/',//半开半闭
            'egt&lt'              => '/^\[([^,]){1,},([^,]){1,}\)$/',//半闭半开
            'egt&elt'             => '/^\[([^,]){1,},([^,]){1,}\]$/',//闭区间
            'in'                  => '/^\{(\S{1,})(\S{1,},)?\}$/', //in
            'notin'               => '/^!\{(\S{1,})(\S{1,},)?\}$/', //notin
            'egt'                 => '/^>=/',
            'elt'                 => '/^<=/',
            'neq'                 => '/^!=/',
            'gt'                  => '/^>/',
            'lt'                  => '/^</',
            self::SQL_LIKE_EXP    => '/^like\((\S{1,})(\S{1,},)?\)$/', //like
            self::SQL_NOTLIKE_EXP => '/^!like\((\S{1,})(\S{1,},)?\)$/', //notlike
        ];

    public function __set($k, $v)
    {
        if (strpos($k, '_') !== false) {
            $field = substr($k, 0, strrpos($k, '_'));
            $exp   = substr($k, strrpos($k, '_') + 1);
            if (isset(self::$_comparison[$exp])) {
                $this->setQuery($field, $v, $exp);
            } else {
                $this->$k = $v;
            }
        } else {
            $this->$k = $v;
        }
    }

    public function __call($method, $arg)
    {
        if (strpos($method, 'set') === 0) {
            $k        = strtolower('_' . str_replace('set', '', $method));
            $arg[0]   = rawurldecode($arg[0]);
            $this->$k = $arg[0];

            return $this;
        } else {
            if (strpos($method, 'get') === 0) {
                $k = strtolower('_' . str_replace('get', '', $method));

                return $this->$k;
            } else {
                if (in_array(strtolower($method), self::$mysqlFun, true)) {
                    // 统计查询的实现
                    $field = isset($arg[0]) ? $arg[0] : '*';

                    return $this->setField(strtoupper($method) . '(' . $field . ') AS easy_' . $method)->fetchOne();
                }
            }
        }
    }


    public function setCond($field, array $cond)
    {
        $exp = $cond[0];
        $v   = $cond[1];

        return $this->setQuery($field, $v, $exp);
    }

    protected function setQuery($k, $v, $exp = self::SQL_EQ_EXP)
    {
        if ($exp != self::SQL_EQ_EXP) {
            unset($this->_fields[$k][self::SQL_EQ_EXP]);
            unset($this->_values[$k][self::SQL_EQ_EXP]);
        }
        if (in_array($exp, self::$inExpArr)) {
            $val                     = self::$_comparison[$exp];
            $vArr                    = explode(',', $v);
            $this->_values[$k][$exp] = implode(',', $vArr);
            foreach ($vArr as &$tempV) {
                $tempV = '?';
            }
            $this->_fields[$k][$exp] = $val . ' ( ' . implode(',', $vArr) . ' ) ';
        } elseif (in_array($exp, self::$likeExpArr)) {
            $val                     = self::$_comparison[$exp] . ' ? ';
            $this->_fields[$k][$exp] = $val;
            $this->_values[$k][$exp] = "%" . $v . "%";
        } else {
            $val                     = self::$_comparison[$exp] . ' ? ';
            $this->_fields[$k][$exp] = $val;
            $this->_values[$k][$exp] = $v;
        }

        return $this;
    }

    public function getWhere()
    {
        krsort($this->_fields);
        krsort($this->_values);
        $orFields  = [];
        $orValues  = [];
        $andFields = [];
        $andValues = [];
        foreach ($this->_fields as $key => $field) {
            if (!in_array($key, $this->_orFields)) {
                $andFields[$key] = $field;
                $andValues[$key] = $this->_values[$key];
            } else {
                $orFields[$key] = $field;
                $orValues[$key] = $this->_values[$key];
            }
        }
        list($andWhere, $andValues) = $this->explainWhere($andFields, $andValues);
        list($orWhere, $orValues) = $this->explainWhere($orFields, $orValues, 'or');
        $where  = '';
        $values = [];
        if ($andWhere) {
            $where  = ' where (' . $andWhere . ')';
            $values = array_merge($values, $andValues);
        }
        if ($orWhere) {
            $where  = $where ? $where . ' and (' . $orWhere . ')' : ' where (' . $orWhere . ')';
            $values = array_merge($values, $orValues);
        }
        if ($this->_whereStr) {
            $where = empty($where) ? ' where ' . $this->_whereStr : $where . ' and ' . $this->_whereStr;
        }

        return [
            $where,
            $values,
        ];
    }

    private function explainWhere($fields, $fieldVals, $condition = 'and')
    {
        krsort($fields);
        krsort($fieldVals);
        if (empty($fields)) {
            return ['', []];
        }
        $values = [];
        $where  = '';
        foreach ($fields as $key => $field) {
            $keyTmp   = strpos($key, '.') !== 0 ? ' ' . $key . ' ' : ' `' . $key . '`';
            $tmpWhere = !empty($where) ? ' ' . $condition . ' ( ' : ' ( ';
            $i        = 0;
            foreach ($field as $exp => $v) {
                $tmpWhere .= ($i > 0 ? ' and ' : '') . $keyTmp . $v;
                $i++;
                if (in_array($exp, self::$inExpArr)) {
                    $tmpvArr = explode(',', $fieldVals[$key][$exp]);
                    foreach ($tmpvArr as $tmpv) {
                        $values[] = $tmpv;
                    }
                } else {
                    $values[] = $fieldVals[$key][$exp];
                }
            }
            $tmpWhere .= ' ) ';
            $where .= $tmpWhere;
        }

        return [
            $where,
            $values,
        ];
    }

    protected function sqlKeyFilter(&$kArray)
    {
        foreach ($kArray as $k => $v) {
            $k = strtolower($k);
            if ($k == 'limit') {
                $this->setLimit($kArray['limit']);
                unset($kArray['limit']);
            }
            if ($k == 'order') {
                $this->setOrder($kArray['order']);
                unset($kArray['order']);
            }
            if ($k == "group") {
                $this->setGroup($kArray['group']);
                unset($kArray['group']);
            }
            if ($k == '_where') {
                $this->_whereStr = ' (' . $v . ') ';
                unset($kArray[$k]);
            }
        }
    }


    private function setQueryFromVal($k, $v)
    {
        $isFind = false;
        foreach (self::$_valComparison as $xexp => $reg) {
            if (preg_match($reg, $v, $match) === 1) {
                if (!isset(self::$_comparison[$xexp])) {
                    $exp1  = substr($xexp, 0, strrpos($xexp, '&'));
                    $exp2  = substr($xexp, strrpos($xexp, '&') + 1);
                    $vstr  = substr($v, 1, strlen($v) - 2);
                    $vstr1 = substr($vstr, 0, strrpos($vstr, ','));
                    $vstr2 = substr($vstr, strrpos($vstr, ',') + 1);
                    $this->setQuery($k, $vstr1, $exp1);
                    $this->setQuery($k, $vstr2, $exp2);
                } else {
                    if (in_array($xexp, self::$inExpArr)) {
                        if ($xexp == self::SQL_IN_EXP) {
                            $v = substr($v, 1, strlen($v) - 2);
                        } else {
                            $v = substr($v, 2, strlen($v) - 3);
                        }
                    } elseif (in_array($xexp, self::$likeExpArr)) {
                        $v = $match[1];
                    } else {
                        $v = str_replace(self::$_comparison[$xexp], '', $v);
                    }
                    $this->setQuery($k, $v, $xexp);
                }
                $isFind = true;
                break;
            }
        }
        if (!$isFind) {
            $this->setQuery($k, $v);
        }

        return false;
    }

    public function getFields()
    {
        return isset($this->_field) ? $this->_field : '*';
    }

    public function getLimit()
    {
        if (isset($this->_limit) && !empty($this->_limit) && strpos($this->_limit, ',') === false) {
            return $this->_limit;
        }
        $start = 0;
        $limit = 20;
        if (isset($this->_limit) && !empty($this->_limit) && preg_match('/^\d{1,},\d{1,}$/', $this->_limit) === 1) {
            $limit = $this->_limit;
            $start = substr($limit, 0, strrpos($limit, ','));
            $limit = substr($limit, strrpos($limit, ',') + 1);

            return [$start, $limit];
        }

        return null;
    }

    public function getOrder()
    {
        if (property_exists($this, '_order') && !empty($this->_order)) {
            return ' order by ' . $this->_order;
        } else {
            return '';
        }
    }

    public function getGroup()
    {
        if (property_exists($this, '_group') && !empty($this->_group)) {
            return ' group by ' . $this->_group;
        } else {
            return '';
        }
    }

    public function setOrField(array $field)
    {
        $this->_orFields = $field;

        return $this;
    }

    protected function prepareQuery($sql = '', $where = [])
    {
        $values = [];
        if (empty($sql) && empty($where)) {
            list($where, $values) = $this->getWhere();
            $table    = $this->getTable();
            $fields   = $this->getFields();
            $order    = $this->getOrder();
            $group    = $this->getGroup();
            $sqlQuery = "select " . $fields . " from " . $table . $where . $group . $order;
        } elseif (!empty($sql) && empty($where)) {
            $sqlQuery = $sql;
            if (strpos(strtolower($sqlQuery), ' where ') === false) {
                list($where, $values) = $this->getWhere();
                $order    = $this->getOrder();
                $group    = $this->getGroup();
                $sqlQuery = $sqlQuery . $where . $group . $order;
            }
        } elseif (empty($sql) && !empty($where)) {
            $this->where($where);
            list($where, $values) = $this->getWhere();
            $table    = $this->getTable();
            $fields   = $this->getFields();
            $order    = $this->getOrder();
            $group    = $this->getGroup();
            $sqlQuery = "select " . $fields . " from " . $table . $where . $group . $order;
        } else {
            $this->where($where);
            list($where, $values) = $this->getWhere();
            $order    = $this->getOrder();
            $group    = $this->getGroup();
            $sqlQuery = $sql . $where . $group . $order;
        }

        return [$sqlQuery, $values];
    }

    public function where(array $where)
    {
        $this->sqlKeyFilter($where);
        foreach ($where as $k => $v) {
            $this->setQueryFromVal($k, $v);
        }

        return $this;
    }

    public function table($table)
    {
        $this->setTable(strtolower($table));

        return $this;
    }

    public function order($orderStr)
    {
        $this->setOrder($orderStr);

        return $this;
    }

    public function group($groupStr)
    {
        $this->setGroup($groupStr);

        return $this;
    }

    public function limit($limitStr)
    {
        $this->setLimit($limitStr);

        return $this;
    }


    public static function instance(DbDTO $dbDTO, $pdoParamsDTO = null)
    {
        if (!is_object($pdoParamsDTO)) {
            $pdoParamsDTO = new PdoParamDTO();
        }
        $dbObj = new XPdo($dbDTO, $pdoParamsDTO);

        return $dbObj;
    }

}
